# -*- coding = utf-8 -*-
# @Time    : 2025/4/8 下午9:33
# @Author  : yqk
# @File    : 111.py
# @Software: PyCharm
# -*- coding: utf-8 -*-
# @Time    : 2025/4/8 下午7:51
# @Author  : yqk
# @File    : 哔哩哔哩登录.py
# @Software: PyCharm
import json
import time
import random
import math
from tenacity import retry, stop_after_attempt, wait_fixed
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from chaojiying_Python.chaojiying import Chaojiying_Client
from selenium.common.exceptions import StaleElementReferenceException, ElementClickInterceptedException


# 浏览器指纹伪装配置
def configure_browser_options():
    opt = Options()
    opt.debugger_address = "127.0.0.1:8888"
    opt.add_argument("--disable-blink-features=AutomationControlled")
    opt.add_experimental_option("excludeSwitches", ["enable-automation"])
    opt.add_argument(
        "--user-agent=Mozilla/5.0  (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36")
    opt.add_argument("--force-device-scale-factor=1")
    opt.add_argument("--high-dpi-support=1")
    return opt


# 智能等待系统
class SmartWaiter:
    @staticmethod
    def wait_for_element(locator, timeout=15):
        return WebDriverWait(browser, timeout).until(
            EC.visibility_of_element_located(locator)
        )

    @staticmethod
    def wait_for_clickable(locator, timeout=10):
        return WebDriverWait(browser, timeout).until(
            EC.element_to_be_clickable(locator)
        )


# 验证码处理器
class CaptchaSolver:
    def __init__(self, username, password, soft_id):
        self.chaojiying = Chaojiying_Client(username, password, soft_id)

    @retry(stop=stop_after_attempt(3), wait=wait_fixed(2))
    def get_captcha_coordinates(self, img_data):
        result = self.chaojiying.PostPic(img_data, 9004)
        return result['pic_str']


# 坐标转换引擎
class CoordinateConverter:
    @staticmethod
    def get_element_metrics(element):
        """获取元素精确位置和尺寸"""
        js_code = """
            const rect = arguments[0].getBoundingClientRect();
            return {
                x: rect.x * window.devicePixelRatio, 
                y: rect.y * window.devicePixelRatio, 
                width: rect.width  * window.devicePixelRatio, 
                height: rect.height  * window.devicePixelRatio  
            }
        """
        return browser.execute_script(js_code, element)

    @staticmethod
    def convert_to_click_position(x, y, metrics):
        """转换绝对坐标到元素相对坐标"""
        dpi_scale = browser.execute_script("return  window.devicePixelRatio  || 1")
        offset_x = (x - metrics['x']) / dpi_scale
        offset_y = (y - metrics['y']) / dpi_scale
        return (
            offset_x + random.uniform(-0.5, 0.5),
            offset_y + random.uniform(-0.5, 0.5)
        )


# 人类行为模拟器
class HumanOperator:
    @staticmethod
    def generate_movement_curve(start, end, steps=5):
        """生成贝塞尔曲线移动路径"""
        control_points = [
            start,
            (start[0] + random.randint(-20, 20), start[1] + random.randint(-20, 20)),
            (end[0] + random.randint(-10, 10), end[1] + random.randint(-10, 10)),
            end
        ]
        return [(
            int(sum(p[0] * (t ** i) * ((1 - t) ** (3 - i)) for i, p in enumerate(control_points))),
            int(sum(p[1] * (t ** i) * ((1 - t) ** (3 - i)) for i, p in enumerate(control_points)))
        ) for t in [x / steps for x in range(steps + 1)]]

    @staticmethod
    def perform_human_click(element, x, y):
        """模拟人类点击行为"""
        action = ActionChains(browser)

        # 生成移动路径
        start_pos = (element.location['x'], element.location['y'])
        path = HumanOperator.generate_movement_curve(start_pos, (x, y))

        # 执行移动
        for point in path:
            action.move_by_offset(point[0] - start_pos[0], point[1] - start_pos[1])
            action.pause(random.uniform(0.01, 0.05))
            start_pos = point

            # 点击操作
        action.click_and_hold().pause(random.uniform(0.1, 0.3)).release().perform()


# 主登录流程
def login(username, password, soft_id):
    global browser
    service = Service(executable_path='D:\python-code\chromedriver-win64\chromedriver.exe')
    browser = webdriver.Chrome(service=service, options=configure_browser_options())

    try:
        # 访问首页
        browser.get('https://www.bilibili.com/')

        # 执行反检测脚本
        browser.execute_script("Object.defineProperty(navigator,  'webdriver', {get: () => undefined})")

        # 触发登录
        login_btn = SmartWaiter.wait_for_clickable(
            (By.XPATH, '//*[@id="i_cecream"]/div[2]/div[1]/div[1]/ul[2]/li[1]/li/div[1]/div/span'))
        login_btn.click()

        # 输入凭证
        SmartWaiter.wait_for_element((By.XPATH, '//input[@placeholder="请输入账号"]')).send_keys(username)
        SmartWaiter.wait_for_element((By.XPATH, '//input[@placeholder="请输入密码"]')).send_keys(password)

        # 提交登录
        submit_btn = SmartWaiter.wait_for_clickable((By.XPATH, '//div[contains(@class, "btn-login")]'))
        submit_btn.click()
        time.sleep(1.5)

        # 处理验证码
        captcha_img = SmartWaiter.wait_for_element(
            (By.XPATH, '//div[@class="geetest_panel"]//img[@role="presentation"]'))
        solver = CaptchaSolver(username, password, soft_id)
        coordinates = solver.get_captcha_coordinates(captcha_img.screenshot_as_png)

        # 解析并转换坐标
        metrics = CoordinateConverter.get_element_metrics(captcha_img)
        for loc in coordinates.split(' |'):
            x, y = map(float, loc.split(','))
            offset_x, offset_y = CoordinateConverter.convert_to_click_position(x, y, metrics)
            HumanOperator.perform_human_click(captcha_img, offset_x, offset_y)
            time.sleep(random.uniform(0.8, 1.2))

        # 最终验证
        submit_btn = SmartWaiter.wait_for_clickable((By.XPATH, '//a[contains(@class, "geetest_commit")]'))
        submit_btn.click()

        # 登录验证
        if SmartWaiter.wait_for_element((By.XPATH, '//div[contains(@class, "header-avatar-wrap")]'), 15):
            print("✅ 登录成功")
        else:
            raise Exception("登录状态验证失败")

    except Exception as e:
        print(f"❌ 登录失败: {str(e)}")
        browser.save_screenshot('error_screenshot.png')
    finally:
        browser.quit()


if __name__ == '__main__':
    with open(r'D:\python-code\chaojiying_Python\password.json', 'r', encoding='utf-8') as f:
        info = json.loads(f.read())
    login(info['username'], info['password'], info['soft_id'])